#include <QApplication>
#include <QLabel>
#include <QThread>
#include <QDebug>
#include <iostream>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}

class Decoder : public QObject {
    Q_OBJECT

public:
    Decoder(QObject *parent = nullptr) : QObject(parent), stopRequested(false) {
        avformat_network_init();
        pFormatContext = avformat_alloc_context();
        swsContext = nullptr;
    }

    ~Decoder() {
        stopRequested = true;
        decodeThread.quit();
        decodeThread.wait();
        if (swsContext) {
            sws_freeContext(swsContext);
        }
        if (pCodecContext) {
            avcodec_free_context(&pCodecContext);
        }
        if (pFormatContext) {
            avformat_close_input(&pFormatContext);
            avformat_free_context(pFormatContext);
        }
    }

    bool open(const QString &filename) {
        if (avformat_open_input(&pFormatContext, filename.toStdString().c_str(), nullptr, nullptr) < 0) {
            return false;
        }

        if (avformat_find_stream_info(pFormatContext, nullptr) < 0) {
            return false;
        }

        videoStream = -1;
        for (unsigned i = 0; i < pFormatContext->nb_streams; i++) {
            if (pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                videoStream = i;
                break;
            }
        }

        if (videoStream == -1) {
            return false;
        }

        AVCodecParameters *codecParameters = pFormatContext->streams[videoStream]->codecpar;
        AVCodec *codec = avcodec_find_decoder(codecParameters->codec_id);
        if (!codec) {
            return false;
        }

        pCodecContext = avcodec_alloc_context3(codec);
        if (!pCodecContext) {
            return false;
        }

        if (avcodec_parameters_to_context(pCodecContext, codecParameters) < 0) {
            return false;
        }

        if (avcodec_open2(pCodecContext, codec, nullptr) < 0) {
            return false;
        }

        return true;
    }

    void startDecoding() {
        decodeThread = QThread(this);
        connect(&decodeThread, &QThread::started, this, &Decoder::decodeLoop);
        moveToThread(&decodeThread);
        decodeThread.start();
    }

    void stopDecoding() {
        stopRequested = true;
    }

signals:
    void frameReady(const QImage &frame);

private slots:
    void decodeLoop() {
        AVPacket packet;
        av_init_packet(&packet);

        while (!stopRequested) {
            if (av_read_frame(pFormatContext, &packet) >= 0) {
                if (packet.stream_index == videoStream) {
                    int response = avcodec_send_packet(pCodecContext, &packet);
                    if (response < 0) {
                        continue;
                    }

                    AVFrame *pFrame = av_frame_alloc();
                    response = avcodec_receive_frame(pCodecContext, pFrame);
                    if (response == 0) {
                        QImage image = frameToQImage(pFrame);
                        emit frameReady(image);
                    }

                    av_frame_free(&pFrame);
                }
            } else {
                break;
            }
            av_packet_unref(&packet);
        }
    }

private:
    QImage frameToQImage(AVFrame *pFrame) {
        if (!swsContext) {
            swsContext = sws_getContext(pCodecContext->width, pCodecContext->height, pCodecContext->pix_fmt,
                                         pCodecContext->width, pCodecContext->height, AV_PIX_FMT_RGB24,
                                         SWS_BILINEAR, nullptr, nullptr, nullptr);
        }

        AVFrame *pFrameRGB = av_frame_alloc();
        int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, pCodecContext->width, pCodecContext->height, 1);
        uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
        av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer, AV_PIX_FMT_RGB24, pCodecContext->width, pCodecContext->height, 1);

        sws_scale(swsContext, (const uint8_t * const *)pFrame->data, pFrame->linesize, 0, pCodecContext->height, pFrameRGB->data, pFrameRGB->linesize);

        QImage image(buffer, pCodecContext->width, pCodecContext->height, QImage::Format_RGB888);

        av_frame_free(&pFrameRGB);
        av_free(buffer);

        return image;
    }

    AVFormatContext *pFormatContext = nullptr;
    AVCodecContext *pCodecContext = nullptr;
    SwsContext *swsContext = nullptr;
    int videoStream = -1;
    QThread decodeThread;
    bool stopRequested;
};

class VideoWidget : public QLabel {
public:
    void displayImage(const QImage &image) {
        setPixmap(QPixmap::fromImage(image));
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    Decoder decoder;
    VideoWidget videoWidget;

    if (decoder.open("path/to/video.mp4")) {
        connect(&decoder, &Decoder::frameReady, &videoWidget, &VideoWidget::displayImage);
        decoder.startDecoding();
    } else {
        qDebug() << "Failed to open video file.";
        return -1;
    }

    videoWidget.resize(640, 480); // Set the size of the video widget
    videoWidget.show();

    return app.exec();
}
